/*

* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     Smart-Shop开源商城系统/ Java商城-首选电商平台系统 请务必保留此注释头信息
* |     开源地址: https://gitee.com/ningbo-qishan/gruul-mall
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     可正常分享和学习源码,未经授权 不可商用！
* |     商业版中台购买/开源版商业授权 联系技术客服
* |     官网:  https://www.73app.cn/
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     Copyright (c) 2016-2999 宁波启山智软科技有限公司
* |     版权所有 ,侵权必究！
* ------------------------------------------------------------------------

*/
package com.medusa.gruul.solitaire.web.utils.http;

import com.google.gson.Gson;
import com.medusa.gruul.common.core.exception.ServiceException;
import com.medusa.gruul.solitaire.api.constant.HttpConstant;
import com.medusa.gruul.solitaire.web.utils.http.request.ApiRequest;
import com.medusa.gruul.solitaire.web.utils.http.response.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 微信机器人 HTTP 发送端
 *
 * @author biezhi
 * @date 2018/1/18
 */
@Slf4j
public class HttpClient {

	private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

	private static Map<String, List<Cookie>> cookieStore = new ConcurrentHashMap<>();

	private OkHttpClient client;
	private OkHttpClient clientWithTimeout;

	public static HttpClient getClient(){
		return new HttpClient(OkHttpUtil.client(null));
	}

	public HttpClient(OkHttpClient client) {
		this.client = client;
		System.setProperty("https.protocols", "TLSv1");
		System.setProperty("jsse.enableSNIExtension", "false");
	}

	/**
	 * 重新恢复Cookie
	 *
	 * @param cookieStore
	 */
	public static void recoverCookie(Map<String, List<Cookie>> cookieStore) {
		HttpClient.cookieStore.clear();
		HttpClient.cookieStore = cookieStore;
	}

	public static Map<String, List<Cookie>> cookieStore() {
		return cookieStore;
	}

	public <T extends ApiRequest, R extends ApiResponse> void send(final T request, final Callback<T, R> callback) {
		OkHttpClient client = getOkHttpClient(request);
		client.newCall(createRequest(request)).enqueue(new okhttp3.Callback() {
			@Override
			public void onResponse(Call call, Response response) {
				try {
					String body = response.body().string();
					if (log.isDebugEnabled()) {
						log.debug("Response:\r\n{}", body);
					}
					callback.onResponse(request, (R) new ApiResponse(body));
				} catch (Exception e) {
					IOException ioEx = e instanceof IOException ? (IOException) e : new IOException(e);
					callback.onFailure(request, ioEx);
				}
			}

			@Override
			public void onFailure(Call call, IOException e) {
				callback.onFailure(request, e);
			}
		});
	}

	public <T extends ApiRequest, R extends ApiResponse> R send(final ApiRequest<T, R> request) {
		try {
			OkHttpClient client = getOkHttpClient(request);
			Request okHttpRequest = createRequest(request);
			Response response = client.newCall(okHttpRequest).execute();
			String body = response.body().string();

			if (log.isDebugEnabled()) {
				log.debug("Response :\r\n{}", body);
			}
			return (R) new ApiResponse(body);
		} catch (IOException e) {
			throw new ServiceException(e.getMessage(),e.getCause());
		}
	}

	private OkHttpClient getOkHttpClient(ApiRequest request) {
		OkHttpClient client = timeout(request);
		if (request.isNoRedirect()) {
			return client.newBuilder().followRedirects(false).followSslRedirects(false).build();
		}
		return cookie(client);
	}

	/**
	 * 设置超时
	 *
	 * @param request
	 * @return
	 */
	private OkHttpClient timeout(ApiRequest request) {
		int timeoutMillis = request.getTimeout() * 1000;
		if (client.readTimeoutMillis() == 0 || client.readTimeoutMillis() > timeoutMillis) {
			return client;
		}
		if (null != clientWithTimeout && clientWithTimeout.readTimeoutMillis() > timeoutMillis) {
			return clientWithTimeout;
		}
		clientWithTimeout = client.newBuilder().readTimeout(timeoutMillis + 1000, TimeUnit.MILLISECONDS).build();
		return clientWithTimeout;
	}

	private OkHttpClient cookie(OkHttpClient client) {
		return client.newBuilder().cookieJar(new CookieJar() {
			@Override
			public void saveFromResponse(HttpUrl httpUrl, List<Cookie> cookies) {
			}

			@Override
			public List<Cookie> loadForRequest(HttpUrl httpUrl) {
				List<Cookie> cookies = cookieStore.get(httpUrl.host());
				return cookies != null ? cookies : new ArrayList<Cookie>();
			}
		}).build();
	}

	public List<Cookie> cookies() {
		List<Cookie>             cookies = new ArrayList<Cookie>();
		Collection<List<Cookie>> values  = cookieStore.values();
		for (List<Cookie> value : values) {
			cookies.addAll(value);
		}
		return cookies;
	}

	public String cookie(String name) {
		for (Cookie cookie : cookies()) {
			if (cookie.name().equalsIgnoreCase(name)) {
				return cookie.value();
			}
		}
		return null;
	}

	private Request createRequest(ApiRequest request) {
		Request.Builder builder = new Request.Builder();
		if (HttpConstant.GET.equalsIgnoreCase(request.getMethod())) {
			builder.get();
			if (null != request.getParameters() && request.getParameters().size() > 0) {
				Set<String>   keys = request.getParameters().keySet();
				StringBuilder sbuf = new StringBuilder(request.getUrl());
				if (request.getUrl().contains("=")) {
					sbuf.append("&");
				} else {
					sbuf.append("?");
				}
				for (String key : keys) {
					sbuf.append(key).append('=').append(request.getParameters().get(key)).append('&');
				}
				request.url(sbuf.substring(0, sbuf.length() - 1));
			}
		} else {
			builder.method(request.getMethod(), createRequestBody(request));
		}
		builder.url(request.getUrl());
		if (log.isDebugEnabled()) {
			log.debug("Request : {}", request.getUrl());
		}
		if (null != request.getHeaders()) {
			builder.headers(request.getHeaders());
		}
		return builder.build();
	}

	private RequestBody createRequestBody(ApiRequest<?, ?> request) {
		if (request.isMultipart()) {
			MediaType contentType = MediaType.parse(request.getContentType());
			MultipartBody.Builder builder     = new MultipartBody.Builder().setType(MultipartBody.FORM);

			for (Map.Entry<String, Object> parameter : request.getParameters().entrySet()) {
				String name  = parameter.getKey();
				Object value = parameter.getValue();
				if (value instanceof byte[]) {
					builder.addFormDataPart(name, request.getFileName(), RequestBody.create(contentType, (byte[]) value));
				} else if (value instanceof File) {
					builder.addFormDataPart(name, request.getFileName(), RequestBody.create(contentType, (File) value));
				} else if (value instanceof RequestBody) {
					builder.addFormDataPart(name, request.getFileName(), (RequestBody) value);
				} else {
					builder.addFormDataPart(name, String.valueOf(value));
				}
			}
			return builder.build();
		} else {
			if (request.isJsonBody()) {
				String json =new Gson().toJson(request.getParameters());
				if (log.isDebugEnabled()) {
					log.debug("Request Body:\r\n{}", json);
				}
				return RequestBody.create(JSON, json);
			} else {
				FormBody.Builder builder = new FormBody.Builder();
				for (Map.Entry<String, Object> parameter : request.getParameters().entrySet()) {
					builder.add(parameter.getKey(), String.valueOf(parameter.getValue()));
				}
				return builder.build();
			}

		}
	}

}
